package vip.xumy.core.utils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.file.Files;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import lombok.extern.log4j.Log4j2;
import vip.xumy.core.exception.CoreException;
import vip.xumy.core.golbal.GlobalConstant;

/**	All rights reserved
 *	author:mengyxu
 *	date:2019年11月22日
 */

@Log4j2
public class FileUtil {
	private static final long MAX_SIZE_READ_CONTENT = 10 * 1024L * 1024L;
	public static final String ZIP = "zip";
	public static final String RAR = "rar";
	
	public static BufferedReader readFile(String filePath) {
		File file = new File(filePath);
		return readFile(file);
	}

	public static BufferedReader readFile(File file) {
		try {
			if (file.exists()){
				return new BufferedReader(new InputStreamReader((new FileInputStream(file)), "UTF-8"));
			}
		} catch (IOException e) {
			log.error("Read file error.", e);
		}
		return null;
	}
	
	public static byte[] read(String filePath) {
		File file = new File(filePath);
		return read(file);
	}
	
	public static byte[] read(File file) {
		if (file.exists()){
			try(
				InputStream in = new FileInputStream(file);
			) {
				byte[] body = new byte[in.available()];
				in.read(body);
				return body;
			} catch (IOException e) {
				log.error("Read file error.", e);
			}
		}
		return null;
	}
	
	public static byte[] read(InputStream is) {
		byte[] body = null;
		try{
			byte[] buf = new byte[20480];
			int len = -1;
			while ((len = is.read(buf)) != -1) {
				if(body == null) {
					body = new byte[len];
					System.arraycopy(buf, 0, body, 0, len);
				}else {
					byte[] tmp = body;
					body = new byte[len + tmp.length];
					System.arraycopy(tmp, 0, body, 0, tmp.length);
					System.arraycopy(buf, 0, body, tmp.length, len);
				}
			}
			return body;
		} catch (IOException e) {
			log.error("Read file error.", e);
		}
		return null;
	}

	/**
	 * Read file and return content in string;
	 * @param file
	 * @return
	 * @throws CoreException 
	 */
	public static String readFileToString(File file) throws CoreException{
		if (file.exists() && file.length() > MAX_SIZE_READ_CONTENT) {
			throw new CoreException(MessageFormat.format("File [{0}] is larger than 10M!", file.getAbsolutePath()));
		}
		
		StringBuilder builder = new StringBuilder();
		String line = null;
		
		try(
			BufferedReader reader = readFile(file);
		){
			if (reader == null){
				log.error(MessageFormat.format("Read file [{0}] failed! ", file.getAbsolutePath()));
				return null;
			}
			while ((line = reader.readLine()) != null){
				builder.append(line);
			}
		} catch (IOException e) {
			log.error(MessageFormat.format("Read file [{0}] failed! ", file.getAbsolutePath()));
			return null;
		}
		
		return builder.toString();
	}
	
	/**
	 * @see readFileToString
	 * @param filePath
	 * @return
	 * @throws CoreException
	 */
	public static String readFileToString(String filePath) throws CoreException{
		return readFileToString(new File(filePath));
	}
	
	public static Scanner scannerFile(String filePath) {
		File file = new File(filePath);
		
		return scannerFile(file);
	}
	
	public static Scanner scannerFile(File file) {
		try {
			if (file.exists()){
				return new Scanner((new FileInputStream(file)), "UTF-8");
			}
		} catch (IOException e) {
			log.error("Scanner file error.", e);
		}
		
		return null;
	}
	
	/**
	 * Loop and return the file list for the root path.
	 * @param root
	 * @return
	 */
	public static List<File> listFiles(File root) {
		List<File> fileList = new ArrayList<>();
		File[] configs = root.listFiles();
		if (configs != null) {
			for (File f : configs) {
				addFileList(fileList, f);
			}
		}
		return fileList;
	}

	private static void addFileList(List<File> results, File file) {
		if (file.isFile()) {
			results.add(file);

		} else if (file.isDirectory()) {
			results.addAll(listFiles(file));
		} else {
			log.error("Skip " + file + " is not a valid file.");
		}
	}
	
	public static boolean mkdir(String dirPath){
		File dir = new File(dirPath);
		
		if (!dir.exists()) {
			return dir.mkdirs();
		}
		
		return true;
	}
	
	/**
	 * Write a file with BufferedOutputStream.
	 * No close method called in this method for parameter 'os'.
	 * The caller should be close it itself. 
	 * @param file
	 * @param os
	 * @throws CoreException
	 */
	public static void write(File file, OutputStream os) throws CoreException{
		BufferedOutputStream bos = new BufferedOutputStream(os);

		try (
				BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
		) {
			byte[] buffered = new byte[1024 * 256];
			int len = 0;
			int count = 0;
			while((len = bis.read(buffered)) != -1){
				bos.write(buffered, 0, len);
				
				if (++count % 100 == 0){
					bos.flush();
				}
			}
			bos.flush();
		} catch (Exception e) {
			throw new CoreException(e.getMessage(), e);
		}
	}


	/**
	 * Write the content line by line to the file.
	 * @param filePath
	 * @param contentList
	 * @return 
	 * @throws CoreException
	 */
	public static File writeToFile(String fullFileName, List<String> contentList) throws CoreException{
		if (contentList == null || contentList.isEmpty()){
			return null;
		}

		return writeToFile(fullFileName, contentList.toArray(new String[]{}));
	}


	/**
	 * Write the content line by line to the file.
	 * @param filePath
	 * @param contentList
	 * @return 
	 * @throws CoreException
	 */
	public static File writeToFile(String fullFileName, String... lines) throws CoreException{
		if (lines == null || lines.length == 0){
			return null;
		}
		
		File newFile = new File(fullFileName);
		File parent = newFile.getParentFile();
		if(!parent.exists()){
			parent.mkdirs();
		}

		try (
			//It will be auto closed at the end with try-with-resources structure.
			BufferedWriter bufferedWriter = new BufferedWriter(
					new OutputStreamWriter(new FileOutputStream(newFile, true), GlobalConstant.ENCODING_UTF8));
		){
			for (String content : lines) {
				bufferedWriter.write(content);
				bufferedWriter.newLine();
			}
			bufferedWriter.flush();
		} catch (IOException e) {
			log.error("FileUtil.writeToFile(): IOException:"+e.getMessage());
			throw new CoreException("FileUtil.writeForExistFile(): IOException:",e);
		}
		
		return newFile;
	}
	
	/**
	 * Write the content of inputStream to the file.
	 * @param filePath
	 * @param inputStream
	 * @return 
	 * @throws CoreException
	 */
	public static void writeToFile(String fullFileName, InputStream is) throws CoreException{
		File newFile = new File(fullFileName);
		File parent = newFile.getParentFile();
		if(!parent.exists()){
			parent.mkdirs();
		}
		try (
			FileOutputStream fos = new FileOutputStream(newFile);
		){
			byte[] buffered = new byte[1024 * 256];
			int len = 0;
			int count = 0;
			while((len = is.read(buffered)) != -1){
				fos.write(buffered, 0, len);
				
				if (++count % 100 == 0){
					fos.flush();
				}
			}
			fos.flush();
		} catch (IOException e) {
			log.error("FileUtil.writeToFile(): IOException:"+e.getMessage());
			throw new CoreException("FileUtil.writeForExistFile(): IOException:",e);
		}
	}
	
	/**
	 * Remove suffix for a file name.
	 * @param fileName
	 * @return
	 */
	public static String removeSuffix(String fileName){
		String dest = fileName;
		
		if (fileName.lastIndexOf('.') > 0){
			dest = fileName.substring(0, fileName.lastIndexOf('.'));
		}
		
		return dest;
	}
	
	/**
	 * Copy a file to b file.
	 * @param src
	 * @param dest
	 * @return	True for successfully. False for failed or empty parameter.
	 * @throws CoreException
	 */
	public static boolean copy(File src, File dest) throws CoreException{
		if (src == null || dest == null){
			return false;
		}
		
		try (
			OutputStream os = new FileOutputStream(dest);
		){
			write(src, os);
		} catch (IOException e) {
			String msg = MessageFormat.format("Cannot copy file from [{0}] to [{1}]", src.getAbsolutePath(), dest.getAbsolutePath());
			throw new CoreException(msg);
		}
		
		return true;
	}
	
	private FileUtil(){
		//A private constructor to hide the implicit public one.
	}
	public static void decompression(File srcFile, File dstFile) throws CoreException{
		String fileType = getTypeByStream(srcFile);
		if(ZIP.equals(fileType)){
			unzip(srcFile,dstFile);
		}
	}
	public static void unzip(File srcFile, File dstFile) throws CoreException {
		if (srcFile.exists()) {
			if (!dstFile.getParentFile().exists()) {
				// 创建文件父目录
				dstFile.getParentFile().mkdirs();
			}
			
			try (
					ZipInputStream zis = new ZipInputStream(new FileInputStream(srcFile));
					BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dstFile));
			){
				ZipEntry entry = null;
				while ((entry = zis.getNextEntry()) != null && !entry.isDirectory()) {
					// 写入文件
					int read = 0;
					byte[] buffer = new byte[1024 * 10];
					while ((read = zis.read(buffer, 0, buffer.length)) != -1) {
						bos.write(buffer, 0, read);
					}
					bos.flush();
				}
				zis.closeEntry();
			} catch (IOException e) {
				throw new CoreException(e);
			}
		}
	}
	/**
	 * 根据文件流读取文件真实类型
	 * 
	 * @param is
	 * @return
	 * @throws FileNotFoundException 
	 */
	public static String getTypeByStream(File f) throws CoreException {
		byte[] b = new byte[4];
		try (FileInputStream is = new FileInputStream(f);) {
			is.read(b, 0, b.length);
		} catch (IOException e) {
			throw new CoreException(e);
		}
		
		String hexString = bytesToHexString(b);
		
		if (hexString == null){
			throw new CoreException("Unknown file type.");
		}
		
		String type = hexString.toUpperCase();
		if (type.contains("52617221")) {
			return RAR;
		} else if (type.contains("504B0304")) {
			return ZIP;
		}
		return type;
	}
	/**
	 * byte数组转换成16进制字符串
	 * 
	 * @param src
	 * @return
	 */
	public static String bytesToHexString(byte[] src) {
		StringBuilder stringBuilder = new StringBuilder();
		if (src == null || src.length <= 0) {
			return null;
		}
		for (int i = 0; i < src.length; i++) {
			int v = src[i] & 0xFF;
			String hv = Integer.toHexString(v);
			if (hv.length() < 2) {
				stringBuilder.append(0);
			}
			stringBuilder.append(hv);
		}
		return stringBuilder.toString();
	}

	/**
	 * 
	 * @param files
	 * @return
	 */
	public static boolean deleteFiles(File...files){
		if (files == null || files.length == 0){
			log.warn("No parameter [files] provided!");
			return false;
		}
		
		boolean deleteResult = true;
		for (File file : files) {
			if (file != null) {
				try {
					Files.deleteIfExists(file.toPath());
				} catch (IOException e) {
					log.warn(">(o_o)<~ Cannot delete temporary file: " + file.getAbsolutePath());
					deleteResult = false;
				}
			}
		}
		
		return deleteResult;
	}
	
	public static boolean isAvailableDir(String filePath){
		return isAvailableDir(new File(filePath));
	}
	
	public static boolean isAvailableDir(File dirFile){
		return dirFile.exists() && dirFile.isDirectory(); 
	}
}
